Profile picture

新しい技術や古い技術も気になる優柔不断マン!トキメキ大事に!
旧サイトはこちら!

NextjsでDIコンテナ経由でイベントを発火してみる[typescript]

July 08, 2023

この記事は4分ぐらいで読めるっぽいよ。

背景

仕事でWebUIでアプリを作ることになり、いろいろ勉強中です。
偶然、このブログを作っていた事が色々役に立ちました。
まぁそんなこんなで、コンポーネントをまたいでやり取りするケースが出てきて、
いろいろ考えたわけです。

当方、C#が今のところ一番得意なようで、
もしC#で組むならDIコンテナ✕イベントでやるなと。
ということで、TSでもDIコンテナ✕イベントをやる方法を模索しました。
ホントはMessagePipeのようにDIコンテナの
セットアップが簡単にできるやつがあったら良かったんですが、
見つけられなかった(ぶっちゃけそんなにガチに探してないです、あったら教えてくださいm(_ _)m)
ので、nanoeventsinversifyを組み合わせて見ることにしました。

※当方はtypescriptやweb系技術についてズブの素人です。ご注意ください。

結論(ソース)

Githubに置きました
Dockerで環境構築してください。
Dockerで環境構築する際はぜひこの記事も参考にしてみてください!

また、お初ですが、devcontainer.jsonを設置してあるので、
Github Codespaceを使うとセットアップが自動化されます。
便利な世の中だなぁ...(しみじみ

詳解

DIコンテナとnanoeventsの導入

依存解決

  1. inversifyを導入(記事いっぱいあるため割愛)
  2. nanoeventsを導入
shell
Copy
yarn add nanoevents

イベントをハンドリングするインターフェースとクラスの準備

  1. 新規にIHogeEventProvider.tsxを作成する
IHogeEVentProvider.tsx
Copy
export interface CallbackFunc {
    (push: boolean): void;
}

export interface IHogeEventProvider{
    publish: (push:boolean) => void,
    subscribe: (cbFunc:CallbackFunc) => void
}
  1. 新規にIHogeEvent.tsxを作成する
IHogeEvent.tsx
Copy
export interface IHogeEvent {
    push: (push: boolean) => void;
}
  1. 新規にHogeEventProvider.tsxを作成する
HogeEventProvider.tsx
Copy
import { CallbackFunc, IHogeEventProvider } from "@/interfaces/IHogeEventProvider"
import { IHogeEvent } from "@/interfaces/eventSchemas/IHogeEvent"
import { injectable } from "inversify";
import { Emitter, createNanoEvents } from "nanoevents"
import 'reflect-metadata'

@injectable()
export class HogeEventProvider implements IHogeEventProvider {
    private emitter: Emitter<IHogeEvent>;

    constructor(){
        this.emitter = createNanoEvents<IHogeEvent>();
    }

    public publish(push: boolean) {
        this.emitter.emit('push',push);
    }

    public subscribe(cbFunc: CallbackFunc) {
        this.emitter.on('push',push => {
            cbFunc(push);
        })
    }

}

DIコンテナのセットアップ

  1. tsconfig.jsonを修正

"experimentalDecorators": true"emitDecoratorMetadata": trueを追加

tsconfig.json
Copy
{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "experimentalDecorators": true,    "emitDecoratorMetadata": true,    "plugins": [
      {
        "name": "next"
      }
    ],
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}
  1. next.config.jsを修正
next.config.js
Copy
/** @type {import('next').NextConfig} */
const nextConfig = {
    experimental: {
        instrumentationHook: true
    }
}

module.exports = nextConfig
  1. 新規にTypes.tsを作成する
    この中にはDIコンテナに登録するための型情報リストが格納されると思ってもらって良い
Types.ts
Copy
import { IHogeEventProvider } from "@/interfaces/IHogeEventProvider"
const TYPES = {
    IHogeEventProvider: Symbol.for('IHogeEventProvider'),
  }
  
  export { TYPES }
  1. 新規にinversify.config.tsを作成する
    この中で、DIコンテナの登録処理を行う
inversify.config.ts
Copy
import { Container } from 'inversify'
import { TYPES } from '@/dependencyInjections/Types'
import { IHogeEventProvider } from '@/interfaces/IHogeEventProvider'
import { HogeEventProvider } from '@/implements/HogeEventProvider'

const myContainer = new Container()
myContainer.bind<IHogeEventProvider>(TYPES.IHogeEventProvider).to(HogeEventProvider).inSingletonScope()

export { myContainer }

DIコンテナとイベントの呼び出し方・使い方

Subscriber

例として、LeftComponent.tsxで示す

LeftComponent.tsx
Copy
import React, { useState } from "react";
import { myContainer } from "@/dependencyInjections/inversify.config";
import { HogeEventProvider } from "@/implements/HogeEventProvider";
import { TYPES } from "@/dependencyInjections/Types";
type Props = {};

const LeftComponent = (props: Props) => {
    const eventAction = myContainer.get<HogeEventProvider>(TYPES.IHogeEventProvider);    const [isOn, setIsOn] = useState(false);

    eventAction.subscribe(x => {        setIsOn(!isOn);    });
    return (
        <>
        ひだりのこんぽーねんと<br/>
        {isOn ? "おん" : "おっふ"}<br/>
        <br/>
        </>
    );
};

export default LeftComponent;

8行目でDIコンテナからEventProviderを取り出し、
11-13行目でイベント購読のコールバック処理を記述している

Publisher

例として、RightComponent.tsxで示す

RightComponent.tsx
Copy
import React, { useState } from "react";
import { myContainer } from "@/dependencyInjections/inversify.config";
import { HogeEventProvider } from "@/implements/HogeEventProvider";
import { TYPES } from "@/dependencyInjections/Types";
import { Button } from 'antd';

type Props = {};

const RightComponent = (props: Props) => {
    const eventAction = myContainer.get<HogeEventProvider>(TYPES.IHogeEventProvider);
    return (
        <div>
            右のコンポーネント<br/>
            <Button type="primary"  onClick={x => eventAction.publish(true)} > Button </Button>        </div>
    );
};

export default RightComponent;

10行目でDIコンテナからEventProviderを取り出し、
15行目でイベントの発信を行っている

実行の様子

RightComponentにあるButtonを押すことで、
LeftComponentの表示が切り替わっているのがわかる

もちろんイベントのため、
複数の一斉購読も可能

DIコンテナから使う側はほぼMessagePipeと同じ使用感になったので
結構満足です(`・ω・´)
しかし、DIコンテナの登録が鬼面倒くさいですね...
これなんとかしたいなぁ...

まぁとりあえずこれで比較的キレイ&楽チンに
コンポーネント間通信ができるようになりましたね!
めでたしめでたし?
では(^^)ノシ


このポエムを轟かせたいと思ったらシェアやで

© 2024 yukimakura All rights reserved, Built with Gatsby